package com.spring.ioc.demo.test;

import com.spring.ioc.demo.dao.UserDaoImpl;
import com.spring.ioc.demo.domain.User;
import com.spring.ioc.demo.ioc.BeanDefinition;
import com.spring.ioc.demo.ioc.PropertyValue;
import com.spring.ioc.demo.ioc.RuntimeBeanReference;
import com.spring.ioc.demo.ioc.TypedStringValue;
import com.spring.ioc.demo.service.impl.UserServiceImpl;
import com.sun.deploy.util.StringUtils;
import javafx.beans.binding.ObjectBinding;
import org.apache.commons.collections.map.HashedMap;
import org.apache.commons.dbcp.BasicDataSource;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.junit.Before;
import org.junit.Test;

import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 使用面向对象思维和配置文件的方式去实现容器化管理Bean（请看springIocTest03）
 * @author zero
 * */
public class SpringIocTest02 {
    // 存储单Bean实例的Map容器
    private Map<String,Object> singletonObjects = new HashMap<>();

    // 存储BeanDefinition的容器
    private Map<String,BeanDefinition> beanDefinitions = new HashedMap();

    @Before
    public void loadXml(){
        // 完成XML解析，其实就是完成BeanDefinition的注册
        // XML解析，解析的结果，放入beanDefinitions中
        String location = "beansDemo.xml";
        InputStream inputStream = getInputStream(location);
        // 创建文档对象
        Document document = createDocument(inputStream);
        // 按照spring定义的标签语义去解析Document文档
        registerBeanDefinitions(document.getRootElement());
    }

    private void registerBeanDefinitions(Element rootElement) {
        // 获取<bean>和自定义标签（比如mvc:interceptors）
        List<Element> elements = rootElement.elements();
        for(Element element : elements){
            // 循环获取标签名
            String name = element.getName();
            if("bean".equals(name)){
                // 默认的标签名
                parseDefaultElement(element);
            }else {
                // 解析自定义标签，比如aop:aspect标签
                parseCustomElement(element);
            }
        }
    }

    private void parseCustomElement(Element element) {
        // AOP、TX、MVC标签的解析，都是走该流程
    }

    private void parseDefaultElement(Element beanElement) {
        if (beanElement == null) {
            return;
        }
        try {
            // 获取id属性
            String id = beanElement.attributeValue("id");

            // 获取name属性
            String name = beanElement.attributeValue("name");

            // 获取class属性
            String clazzName = beanElement.attributeValue("class");
            if (clazzName == null || "".equals(clazzName)) {
                return;
            }

            // 获取init-method属性
            String initMethod = beanElement.attributeValue("init-method");
            // 获取scope属性
            String scope = beanElement.attributeValue("scope");
            scope = scope != null && !scope.equals("") ? scope : "singleton";

            // 获取beanName
            String beanName = id == null ? name : id;
            Class<?> clazzType = Class.forName(clazzName);
            beanName = beanName == null ? clazzType.getSimpleName() : beanName;
            // 创建BeanDefinition对象
            // 此次可以使用构建者模式进行优化
            BeanDefinition beanDefinition = new BeanDefinition(beanName,clazzName);
            beanDefinition.setInitMethod(initMethod);
            beanDefinition.setScope(scope);
            // 获取property子标签集合
            List<Element> propertyElements = beanElement.elements();
            for (Element propertyElement : propertyElements) {
                parsePropertyElement(beanDefinition, propertyElement);
            }

            // 注册BeanDefinition信息
            this.beanDefinitions.put(beanName, beanDefinition);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    private void parsePropertyElement(BeanDefinition beanDefinition, Element propertyElement) {
        if (propertyElement == null)
            return;

        // 获取name属性
        String name = propertyElement.attributeValue("name");
        // 获取value属性
        String value = propertyElement.attributeValue("value");
        // 获取ref属性
        String ref = propertyElement.attributeValue("ref");

        // 如果value和ref都有值，则返回
        if (value != null && !value.equals("") && ref != null && !ref.equals("")) {
            return;
        }

        /**
         * PropertyValue就封装着一个property标签的信息
         */
        PropertyValue pv = null;

        if (value != null && !value.equals("")) {
            // 因为spring配置文件中的value是String类型，而对象中的属性值是各种各样的，所以需要存储类型
            TypedStringValue typeStringValue = new TypedStringValue(value);

            Class<?> targetType = getTypeByFieldName(beanDefinition.getClazzName(), name);
            typeStringValue.setTargeType(targetType);

            pv = new PropertyValue(name, typeStringValue);
            beanDefinition.addPropertyValue(pv);
        } else if (ref != null && !ref.equals("")) {

            RuntimeBeanReference reference = new RuntimeBeanReference(ref);
            pv = new PropertyValue(name, reference);
            beanDefinition.addPropertyValue(pv);
        } else {
            return;
        }
    }

    private Class<?> getTypeByFieldName(String clazzName, String name) {
        try {
            Class<?> clazz = Class.forName(clazzName);
            Field field = clazz.getDeclaredField(name);
            return field.getType();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private Document createDocument(InputStream inputStream) {
        Document document = null;
        try {
            SAXReader reader = new SAXReader();
            document = reader.read(inputStream);
        } catch (DocumentException e) {
            e.printStackTrace();
        }
        return document;
    }

    private InputStream getInputStream(String location) {
        return getClass().getClassLoader().getResourceAsStream(location);
    }


    @Test
    public void Test02(){
        // A 程序员其实只想使用业务对象去调用相应的服务
        // B 程序员编写了一段代码给A程序员提供对象  UserServiceImpl userService = getUserService();
        // C 程序员觉得B写的不够好于是它也给A提供了一段代码 getBean();
        // D 程序员
        UserServiceImpl userService = (UserServiceImpl) this.getBean("userService");

        // 使用用户的查询功能
        Map<String,Object> paramMap = new HashMap<>(1);
        paramMap.put("name","张三");
        List<User> results = userService.queryUsers(paramMap);
        System.out.println(results);
    }

    // 由B 程序员编写
    private UserServiceImpl getUserService() {
        UserServiceImpl userService = new UserServiceImpl();
        UserDaoImpl userDao = new UserDaoImpl();
        BasicDataSource dataSource = new BasicDataSource();
        dataSource.setDriverClassName("");
        dataSource.setUrl("");
        dataSource.setUsername("");
        dataSource.setPassword("");
        userDao.setDataSource(dataSource);
        userService.setUserDao(userDao);
        return userService;
    }

    // 由C 程序员编写
    private Object getBeanByName(String beanName){
        if("userService".equals(beanName)){
            return this.getUserService();
        } // TODO 还有很多总之就是不断判断
        return null;
    }

    // 由D 程序员编写
    /**
     *  解决的是扩展性问题：通过配置方式去解决扩展性问题
     *  使用xml配置文件进行Bean的创建
     *  1.管理要new出来的bean的calss信息（要new几个就要配置几个class信息）
     *  2.管理要new出来的bean的属性依赖信息
     *  3.读取静态信息去创建对象
     *  BeanDefinition类--->用来存储<bean>信息，结构是Map<String,BeanDefinition>
     *  4.利用反射从BeanDefinition中获取class对象信息，创建对象
     * */
    private Object getBean(String beanName){
        //  1.首先从singletonObjects集合中获取beanName的实例
        Object singletonObject = this.singletonObjects.get(beanName);
        if(singletonObject != null){
            return singletonObject;
        }
        //  2.如果没有对象则需要获取对应的BeanDefinition信息
        BeanDefinition beanDefinition = this.beanDefinitions.get(beanName);
        //  3.判断是多例还是单例，如果是单例则走单例创建流程，多例走多例创建流程
        if(beanDefinition.isSingleton()){
            singletonObject = doCreateBean(beanDefinition);
            this.singletonObjects.put(beanName,singletonObject);
        }else if(beanDefinition.isPrototype()){
            singletonObject = doCreateBean(beanDefinition);
        }
        return singletonObject;
    }

    private Object doCreateBean(BeanDefinition definition) {
        // 1.Bean的实例化
        Object bean = createBeanByConstructor(definition);
        // 2.Bean的属性填充（依赖注入）
        populateBean(bean,definition);
        // 3.Bean的初始化
        initilizeBean(bean,definition);
        return bean;
    }

    private Object createBeanByConstructor(BeanDefinition definition) {
        // TODO 静态工厂方法、工厂实例方法

        // 构造器方式去创建Bean实例
        try {
            Class<?> clazz = definition.getClassType();
            // 选择无参构造器
            Constructor<?> constructor = clazz.getDeclaredConstructor();
            return constructor.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private void populateBean(Object bean,BeanDefinition beanDefinition){
        List<PropertyValue> propertyValues = beanDefinition.getPropertyValues();
        for(PropertyValue propertyValue : propertyValues){
            String name = propertyValue.getName();
            // 这不是我们需要给Bean设置的value值
            Object value = propertyValue.getValue();
            
            Object valueToUse = resoleValue(value);
            // 为bean的属性赋值
            setProperty(bean,name,valueToUse);
        }
    }

    private Object resoleValue(Object value) {
        if(value instanceof TypedStringValue){
            TypedStringValue typedStringValue = (TypedStringValue) value;
            Class<?> targeType = typedStringValue.getTargeType();
            String stringValue = typedStringValue.getValue();
            if(targeType == Integer.class){
                return Integer.parseInt(stringValue);
            }else if(targeType == String.class){
                return stringValue;
            }//TODO其它类型
        }else if(value instanceof RuntimeBeanReference){
            RuntimeBeanReference runtimeBeanReference = (RuntimeBeanReference) value;
            String ref = runtimeBeanReference.getRef();

            // 递归调用
            return getBean(ref);
        }
        return null;
    }

    private void setProperty(Object bean, String name, Object valueToUse) {
        try {
            Class<?> clazz = bean.getClass();
            Field field = clazz.getDeclaredField(name);
            field.setAccessible(true);
            field.set(bean,valueToUse);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    private void initilizeBean(Object bean,BeanDefinition beanDefinition){
        // TODO 需要针对Aware接口标记的类进行特殊处理

        // TODO 可以进行IntilizingBean接口的处理
        invokeInitMethod(bean,beanDefinition);
    }

    private void invokeInitMethod(Object bean, BeanDefinition beanDefinition) {
        try {
            String initMethod = beanDefinition.getInitMethod();
            if(initMethod == null){
                return;
            }
            Class<?> clazzType = beanDefinition.getClassType();
            Method method = clazzType.getDeclaredMethod(initMethod);
            method.setAccessible(true);
            method.invoke(bean);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
